//! Describes accessors around a homeserver or client data directory.

use std::fs;
use std::io;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use thiserror::Error;

pub const USERDATA_NAME: &str = "user.json";
pub const DEV_AUTH_NAME: &str = "device_signer.json";
pub const DEV_AUTH_TMP_NAME: &str = "device_signer.tmp";

#[derive(Debug, Error)]
pub enum Error {
    #[error("missing user data file")]
    MissingUserData,

    #[error("missing device auth file")]
    MissingDeviceAuth,

    #[error("tried to create datadir where one already exists")]
    DatadirExists,

    #[error("codec: {0}")]
    Codec(#[from] aspect_codec::CodecError),

    #[error("json: {0}")]
    SerdeJson(#[from] serde_json::Error),

    #[error("io: {0}")]
    Io(#[from] io::Error),
}

pub struct Datadir {
    base: PathBuf,
}

impl Datadir {
    /// Creates a new datadir with the provided initial state.
    pub fn create(
        base: PathBuf,
        userdata: &UserData,
        signer: &aspect_ident::IdentSigner,
    ) -> Result<Self, Error> {
        if base.exists() {
            return Err(Error::DatadirExists);
        }

        // Save user data.
        let ud_buf = serde_json::to_vec(userdata)?;
        let mut ud_path = base.clone();
        ud_path.push("user.json");
        fs::write(&ud_path, &ud_buf)?;

        let mut dd = Datadir { base };
        dd.save_device_auth(signer)?;

        Ok(dd)
    }

    /// Opens a datadir and makes assertions about its contents from the config.
    pub fn open(mut base: PathBuf) -> Result<Self, Error> {
        // Check the user data file path.
        base.push(USERDATA_NAME);
        if !base.exists() {
            return Err(Error::MissingUserData);
        }
        base.pop();

        // Check the device authorization data path.
        base.push(DEV_AUTH_NAME);
        if !base.exists() {
            return Err(Error::MissingDeviceAuth);
        }
        base.pop();

        Ok(Self { base })
    }

    pub fn save_device_auth(&mut self, auth: &aspect_ident::IdentSigner) -> Result<(), Error> {
        // Write to a temp file.
        let mut dkt_path = self.base.clone();
        dkt_path.push(DEV_AUTH_TMP_NAME);
        let buf = serde_json::to_vec_pretty(auth)?;
        fs::write(&dkt_path, buf)?;

        // Move it over the real file so that we don't accidentally truncate it.
        let mut dk_path = self.base.clone();
        dk_path.push(DEV_AUTH_NAME);
        fs::rename(dkt_path, dk_path)?;

        Ok(())
    }

    pub fn load_device_auth(&self) -> Result<aspect_ident::IdentSigner, Error> {
        let mut dk_path = self.base.clone();
        dk_path.push(DEV_AUTH_NAME);
        let buf = fs::read(dk_path)?;

        Ok(serde_json::from_slice::<aspect_ident::IdentSigner>(&buf)?)
    }

    pub fn load_user_data(&self) -> Result<UserData, Error> {
        let mut ud_path = self.base.clone();
        ud_path.push("user.json");
        let buf = fs::read(&ud_path)?;
        Ok(serde_json::from_slice(&buf)?)
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UserData {
    pub username: String,
    pub password: String,
    pub homeserver_host: String,
    pub homeserver_port: u16,
}
